﻿Imports System.Xml
Imports System.Net
Imports System.IO
Imports System.Text

<Serializable()>
Public Class TokenDefinition
    Public Property Name As String
    Public Property TokenEntryId As Integer?
    Public Property AllowedOn As List(Of String)
    Public Property Usage As ObjectTypeLookup(Of TokenUsage)
    Public Property SticksTo As List(Of String) 'used for exporting, to know which tokens go on the same line

    Public Sub New()
        AllowedOn = New List(Of String)
        Usage = New ObjectTypeLookup(Of TokenUsage)
        TokenEntryId = Nothing
        SticksTo = New List(Of String)
    End Sub

    Public Function IsAllowedOn(objectType As ObjectType, subType As ObjectSubType) As Boolean
        For Each code As String In AllowedOn
            If code = "ALL:ALL" Then
                Return True
            ElseIf code.StartsWith("ALL:") Then
                If code.EndsWith(":" & subType.ToString) Then
                    Return True
                End If
            ElseIf code.EndsWith(":ALL") Then
                If code.StartsWith(objectType.ToString & ":") Then
                    Return True
                End If
            Else
                If code = objectType.ToString & ":" & subType.ToString Then
                    Return True
                End If
            End If
        Next
        Return False
    End Function

    Public Sub WriteXml(writer As XmlWriter)
        writer.WriteStartElement("token")
        writer.WriteAttributeString("name", Me.Name)

        For Each allow As String In Me.AllowedOn
            Dim parts() As String = allow.Split(":"c)
            writer.WriteStartElement("allowedOn")
            writer.WriteAttributeString("type", parts(0).Trim)
            writer.WriteAttributeString("subType", parts(1).Trim)
            writer.WriteEndElement()
        Next

        For Each usage As TokenUsage In Me.Usage.GetAllValues
            writer.WriteStartElement("usage")
            writer.WriteAttributeString("type", usage.ObjectType.ToString)
            writer.WriteAttributeString("subType", usage.SubType.ToString)
            writer.WriteAttributeString("scope", usage.Scope.ToString)
            If usage.ArgsType <> ArgsType.Standard Then
                writer.WriteAttributeString("argsType", usage.ArgsType.ToString)
                If usage.ArgsTypeData <> "" Then
                    writer.WriteAttributeString("argsTypeData", usage.ArgsTypeData)
                End If
            End If

            XmlHelper.WriteAttributeIfNotDefault(writer, "flags", CInt(usage.Flags).ToString, "0")

            writer.WriteStartElement("description")
            writer.WriteString(usage.Description)
            writer.WriteFullEndElement()

            For Each arg As ArgumentDefinition In usage.Arguments
                writer.WriteStartElement("argument")
                writer.WriteAttributeString("name", arg.Name)
                writer.WriteAttributeString("type", arg.Type.ToString)

                'Begin type data and description
                If arg.TypeData <> "" Then
                    If Not (arg.Type = ArgType.List) Then
                        'simple type data is written as an attribute
                        writer.WriteAttributeString("typeData", arg.TypeData)
                    Else
                        'complex data is written as an element
                        writer.WriteStartElement("typeData")
                        writer.WriteString(arg.TypeData)
                        writer.WriteEndElement()
                    End If
                End If
                If arg.Description <> "" Then
                    writer.WriteStartElement("description")
                    writer.WriteString(arg.Description)
                    writer.WriteEndElement() 'description
                End If
                writer.WriteEndElement() 'argument
            Next

            writer.WriteEndElement()
        Next

        Dim sticksTo As New List(Of String)(Me.SticksTo)
        sticksTo.Sort()
        For Each target As String In sticksTo
            writer.WriteStartElement("sticksTo")
            writer.WriteString(target)
            writer.WriteEndElement()
        Next

        writer.WriteEndElement()
    End Sub

    Public Function GetXmlString() As String
        Dim xml As New StringBuilder

        Dim settings As New XmlWriterSettings
        settings.Indent = True
        settings.IndentChars = "  "
        settings.NewLineChars = vbNewLine
        settings.NewLineHandling = NewLineHandling.Replace
        settings.OmitXmlDeclaration = True

        Dim writer As XmlWriter = XmlWriter.Create(xml, settings)
        WriteXml(writer)
        writer.Close()

        Return xml.ToString
    End Function

    Public Shared Function FromXmlString(Data As String) As TokenDefinition
        Dim ret As TokenDefinition

        Dim reader As New StringReader(Data)
        Dim xmlReader As XmlReader = xmlReader.Create(reader)

        ret = FromXmlReader(xmlReader)

        xmlReader.Close()
        reader.Dispose()

        Return ret
    End Function

    Public Shared Function FromXmlReader(reader As XmlReader) As TokenDefinition
        Dim tokenState As New ParserState

        Do While reader.Read AndAlso Not (reader.NodeType = XmlNodeType.EndElement AndAlso reader.Name.ToLower = "token")
            ProcessNode(tokenState, reader)
        Loop

        Return tokenState.CurrentToken
    End Function

    Private Shared Sub ProcessNode(ByRef state As ParserState, reader As XmlReader)
        Select Case reader.Name
            Case "token"
                If reader.NodeType = XmlNodeType.Element Then
                    state.CurrentToken = New TokenDefinition
                    If reader.MoveToAttribute("name") Then
                        state.CurrentToken.Name = reader.GetAttribute("name")
                    Else
                        Throw TokenParseException.Create("Token has no name", reader)
                    End If
                End If
            Case "allowedOn"
                Dim objType As ObjectType = ObjectType.ALL
                If Not [Enum].TryParse(Of ObjectType)(XmlHelper.GetAttributeOrDefault(reader, "type", "ALL"), objType) Then
                    Throw TokenParseException.Create("Invalid type in allowedOn", reader)
                End If
                Dim subType As ObjectSubType = ObjectSubType.ALL
                If Not [Enum].TryParse(Of ObjectSubType)(XmlHelper.GetAttributeOrDefault(reader, "subType", "ALL"), subType) Then
                    Throw TokenParseException.Create("Invalid subType in allowedOn", reader)
                End If
                state.CurrentToken.AllowedOn.Add(objType.ToString & ":" & subType.ToString)
            Case "usage"
                If reader.NodeType = XmlNodeType.Element Then
                    state.CurrentUsage = New TokenUsage
                    state.CurrentUsage.Definition = state.CurrentToken

                    If Not [Enum].TryParse(Of CreatureTokenScope)(XmlHelper.GetAttributeOrDefault(reader, "scope", "None"), state.CurrentUsage.Scope) Then
                        Throw TokenParseException.Create("Invalid scope in usage", reader)
                    End If
                    If Not [Enum].TryParse(Of ObjectType)(XmlHelper.GetAttributeOrDefault(reader, "type", "ALL"), state.CurrentUsage.ObjectType) Then
                        Throw TokenParseException.Create("Invalid type in usage", reader)
                    End If
                    If Not [Enum].TryParse(Of ObjectSubType)(XmlHelper.GetAttributeOrDefault(reader, "subType", "ALL"), state.CurrentUsage.SubType) Then
                        Throw TokenParseException.Create("Invalid subType in usage", reader)
                    End If
                    If Not [Enum].TryParse(Of ArgsType)(XmlHelper.GetAttributeOrDefault(reader, "argsType", "Standard"), state.CurrentUsage.ArgsType) Then
                        Throw TokenParseException.Create("Invalid argsType in usage", reader)
                    End If
                    state.CurrentUsage.ArgsTypeData = XmlHelper.GetAttributeOrDefault(reader, "argsTypeData", "")
                    state.CurrentUsage.Flags = CType(XmlHelper.GetAttributeOrDefault(Of Integer)(reader, "flags", 0), UsageFlags)

                ElseIf reader.NodeType = XmlNodeType.EndElement Then
                    state.CurrentToken.Usage.RegisterValue(state.CurrentUsage.ObjectType, state.CurrentUsage.SubType, state.CurrentUsage)
                    state.CurrentUsage = Nothing
                End If
            Case "description"

                If reader.NodeType = XmlNodeType.Element Then
                    Dim desc As String = reader.ReadString
                    If desc Is Nothing Then
                        desc = ""
                    End If
                    If state.InArgumentTag Then
                        state.CurrentArgument.Description = desc.Trim
                    Else
                        state.CurrentUsage.Description = desc.Trim
                    End If
                End If
            Case "argument"
                If reader.NodeType = XmlNodeType.Element Then
                    state.InArgumentTag = True
                    Dim argument As New ArgumentDefinition
                    state.CurrentArgument = argument
                    argument.Index = state.CurrentUsage.Arguments.Count
                    argument.Name = XmlHelper.GetAttributeOrDefault(reader, "name", "?") 'reader.GetAttribute("name")
                    Dim typeString As String = reader.GetAttribute("type")
                    If typeString Is Nothing Then
                        Throw TokenParseException.Create("missing argumenType in argument", reader)
                    End If
                    If Not [Enum].TryParse(Of ArgType)(typeString, True, argument.Type) Then
                        Throw TokenParseException.Create("Invalid argumentType in argument", reader)
                    End If

                    argument.TypeData = XmlHelper.GetAttributeOrDefault(reader, "typeData", "")
                    Dim argHandler As IArgHandler = ArgumentManager.instance.GetArgTypeHandler(argument.Type)
                    If Not argHandler.ValidateTypeData(argument.TypeData) Then
                        Throw TokenParseException.Create(String.Format("Invalid typeData format in {0}:{1}. {2}{3}",
                                                                       state.CurrentUsage.ObjectType,
                                                                       state.CurrentUsage.SubType,
                                                                       argument.Name,
                                                                       If(argHandler.TypeDataFormat <> "", ". Format=" & argHandler.TypeDataFormat, "")), reader)
                    End If

                    If reader.IsEmptyElement Then
                        state.InArgumentTag = False
                        state.CurrentUsage.Arguments.Add(state.CurrentArgument)
                        state.CurrentArgument = Nothing
                    End If
                Else
                    state.InArgumentTag = False
                    state.CurrentUsage.Arguments.Add(state.CurrentArgument)
                    state.CurrentArgument = Nothing
                End If
            Case "typeData"
                If reader.NodeType = XmlNodeType.Element Then
                    Dim data As String = reader.ReadString
                    If data Is Nothing Then
                        data = ""
                    End If
                    If state.InArgumentTag Then
                        If state.CurrentArgument.TypeData = "" Then
                            state.CurrentArgument.TypeData = data
                        Else
                            state.CurrentArgument.TypeData &= " " & data
                        End If
                    End If
                End If
            Case "sticksTo"
                If reader.NodeType = XmlNodeType.Element Then
                    Dim target As String = reader.ReadString
                    If Not state.CurrentToken.SticksTo.Contains(target) Then
                        state.CurrentToken.SticksTo.Add(target)
                    End If
                End If
        End Select
    End Sub

    Public Sub UpdateFrom(definition As TokenDefinition)
        Me.Name = definition.Name
        Me.AllowedOn = New List(Of String)(definition.AllowedOn)
        Me.SticksTo = New List(Of String)(definition.AllowedOn)
        Me.Usage = CloneHelper.DeepClone(Of ObjectTypeLookup(Of TokenUsage))(definition.Usage)
    End Sub

    Private Class ParserState
        Public Property CurrentUsage As TokenUsage
        Public Property CurrentToken As TokenDefinition
        Public Property CurrentArgument As ArgumentDefinition
        Public Property InArgumentTag As Boolean
    End Class
End Class
